﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Management;
using System.ServiceProcess;
using Testing.Helpers.Assertion;

namespace Testing.Helpers.Processes
{
    /// <summary>
    /// Helpers for work with Windows service.
    /// </summary>
    public static class WindowsServiceHelper
    {
        /// <summary>
        /// Installs windows service.
        /// </summary>
        /// <param name="servicePath">Service exe file absolute path.</param>
        /// <param name="timeout">Installed service timeout.</param>
        public static void InstallService(string servicePath, int timeout)
        {
            ManageServiceInstallation(servicePath, timeout);
        }

        /// <summary>
        /// Uninstalls windows service.
        /// </summary>
        /// <param name="servicePath">Service exe file absolute path.</param>
        /// <param name="timeout">Uninstalled service timeout.</param>
        public static void UninstallService(string servicePath, int timeout)
        {
            string arguments = String.Concat("/u ", servicePath);
            ManageServiceInstallation(arguments, timeout);
        }


        /// <summary>
        /// Checks if windows service is installed by service name.
        /// </summary>
        /// <param name="name">Service name.</param>
        /// <returns>Service installation status.</returns>
        public static bool CheckServiceIsInstalled(string name)
        {
            var services = new List<ServiceController>(ServiceController.GetServices());
            return services.Exists(s => s.ServiceName == name);
        }

        /// <summary>
        /// Returns service exe file path by service name.
        /// </summary>
        /// <param name="name">Service name.</param>
        /// <returns>Service exe file path. Return null if service with given name is not installed.</returns>
        public static string GetInstalledServiceExePath(string name)
        {
            string path = null;
            var mc = new ManagementClass("Win32_Service");
            foreach (ManagementObject mo in mc.GetInstances()
                                              .Cast<ManagementObject>()
                                              .Where(mo => mo.GetPropertyValue("Name").ToString() == name))
            {
                path = mo.GetPropertyValue("PathName").ToString().Trim('"');
            }

            return path;
        }

        /// <summary>
        /// Gets service status by service name.
        /// </summary>
        /// <param name="serviceName">Service name.</param>
        /// <returns>Service status.</returns>
        public static ServiceControllerStatus GetServiceStatus(string serviceName)
        {
            var service = new ServiceController(serviceName);
            return service.Status;
        }

        /// <summary>
        /// Starts window service.
        /// </summary>
        /// <param name="serviceName">Service name.</param>
        /// <param name="timeout">Started service timeout.</param>
        public static void StartService(string serviceName, int timeout)
        {
            var service = new ServiceController(serviceName);
            if (service.Status != ServiceControllerStatus.Running)
            {
                try
                {
                    var timeSpan = TimeSpan.FromMilliseconds(timeout);
                    service.Refresh();
                    service.Start();
                    service.WaitForStatus(ServiceControllerStatus.Running, timeSpan);
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Failed to start the service «{0}».\n{1}\nService status «{2}»",
                        service.DisplayName, ex.Message, service.Status));
                }
                catch (System.ServiceProcess.TimeoutException ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Failed to start the service «{0}» during a timeout «{1}» mc.\n{2}\nService status {3}",
                        service.DisplayName, timeout, ex.Message, service.Status));
                }
            }
        }

        /// <summary>
        /// Stops windows service.
        /// </summary>
        /// <param name="serviceName">Service name.</param>
        /// <param name="timeout">Stopped service timeout.</param>
        public static void StopService(string serviceName, int timeout)
        {
            var service = new ServiceController(serviceName);
            if (service.Status != ServiceControllerStatus.Stopped)
            {
                try
                {
                    var timeSpan = TimeSpan.FromMilliseconds(timeout);
                    service.Refresh();
                    service.Stop();
                    service.WaitForStatus(ServiceControllerStatus.Stopped, timeSpan);
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Failed to stop the service  «{0}».\n{1}\nService status {2}",
                        service.DisplayName, ex.Message, service.Status));
                }
                catch (System.ServiceProcess.TimeoutException ex)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Failed to stop the service «{0}» during a timeout «{1}» ms.\n{2}\nService status {3}",
                        service.DisplayName, timeout, ex.Message,
                        service.Status));
                }
            }
        }

        private static void ManageServiceInstallation(string arguments, int timeout)
        {
            const string installUtilPath = @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe";
            Process p = null;
            try
            {
                p = Process.Start(installUtilPath, arguments);
                p.WaitForExit(timeout);
                if (!p.HasExited)
                {
                    throw new TestExecutionException(String.Format(CultureInfo.InvariantCulture,
                        "Service installation «{0}» wasn't completed during timeout {1} ms.",
                        arguments, timeout));
                }
            }
            finally
            {
                if (p != null && !p.HasExited)
                {
                    p.Kill();
                }
            }
        }
    }
}
